iT邦幫忙

2022 iThome 鐵人賽

DAY 9
0

[Day09] Clojure Functional Programming與資料操作(2) - reduce

早安!
好不想起床唷...

第八天的文章有說到clojure沒有sum的函式,因為可以用+來取代。

(+ 1 2 3 4)
=> 10

那如果是1 加到 100呢~?
當然不要一個一個打出來啦?

想到其他語言有range的用法,我們可以先用 range 做一個Sequences(序列)裝起來

create Sequences的各種方式可以參考一下clojure小抄

(range 1 101)

=> (1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100)

如同Vectors/Lists/Maps/Sets, Sequences也是屬於collection的一種,

因為今天的目的是想來試試看用reduce操作data,所以我們先用range快速產生Sequence資料囉!

大家有興趣的話~有機會我們以後再找篇幅來深入了解Sequences

https://www.braveclojure.com/do-things/

reduce 實作加法

reduce process each element in a sequence and build a result

reduce處理序列中的每個元素並構建結果

因此,要來出題了!
由1 加到 100你會怎麼用clojure寫呢?

一步一步拆解的話,

  1. 先列出1到100

由文件可知,Range returns a lazy seq of nums from start (inclusive) to end
(exclusive)並不會包含最後一個數,因此 (range 1 101)

(range 1 101)

=> (1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100)

觀察到回傳的值是list,所以不能亂抄!

平常簡單的加法是這樣寫

(+ 1 2 3 4)
=> 10

?硬套用在range直接加的話...

?會壞掉!!

;; 好孩子~不要學我亂寫

(+ (range 1 101))
Execution error (ClassCastException) at java.lang.Class/cast (Class.java:3369).
Cannot cast clojure.lang.LongRange to java.lang.Number

看到錯誤訊息是說Range不能轉為Number

Sequence先轉成Vector

假設 map / reduce / filter很可能是透過接近類似的手法去處理資料(這樣的直覺是來自於對其他語言的map/reduce...等用法的理解),例如ruby的reduce method對陣列每一個值與初始值作用,最後存成一個值

我們可以從昨天講map function的內文提取一下靈感:

(map inc [1 3 5 7 9])
; => (2 4 6 8 10)

map函式會對每個在collection的element套用某個function
例如inc,map讓vector裡的每個數字 +1,並回傳一個新的list。

昨天的例子 [1 3 5 7 9]如果是Vector的data structure話,那我們可以想辦法把Range產生的1..100的Sequence轉成Vector

參考clojure小抄,關鍵字搜尋vec...好像就可以看到有用的東東!

vec: Creates a new vector containing the contents of coll.

先轉成Vector好了

 (vec (range 1 101))
[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100]

轉完之後Vector是否又迫不及待相加呢?

 (+ (vec (range 1 101)))

;; 又壞掉啦!好孩子不要學

 Execution error (ClassCastException) at java.lang.Class/cast (Class.java:3369).
Cannot cast clojure.lang.PersistentVector to java.lang.Number

Vector並不是Number,
我們需要的是像昨天文章提到的概念,
思考的重點在於"對Vector裡的一個一個的值"做想要的操作

(reduce f value collection)

Reference

從clojure doc裡知道,reduce後面要接的是一個有兩個參數的fuction (value和collection),

如果沒有value,就把collection裡的前兩個值讓前面的fuction去操作,
操作完的值再丟給第三個、再丟給第四個...再丟給100個

對照一下map和reduce對於資料的操作:
(map inc [1 3 5 7 9])
(reduce + [1 2 3 4 (中間省略) 97 98 99 100])

再重新思考一次,寫完整句題目:

(reduce +(vec (range 1 101)))
=> 5050

現在,你會用reduce由1 加到 100了吧?:)

耶!

補充:Sequence真正的意義

本段補於第十天(09/24)

和clojure大大討論本文內容後,

我才明白到用ruby的對應function去了解Clojure core library會有其限制,

clojure 的 map / reduce / filter和Ruby最大的差異(同時也是最自由的地方),

就是在iteration function 並不需要在Vector(Ruby的Array)做操作,

而是可以透過sequence來一直串接。所以凡是 sequence 都可以 iterate!(據說這邊水很深~~)

所以,我們的

(reduce +(vec(range 1 101)))

可以再簡化為

(reduce +(range 1 101))

來跑看看執行結果確定一下:

(reduce +(range 1 101))
=>5050

來! 用reduce定義自己的sum

昨天有說到匿名函式的簡寫,

(fn [X Y] (str X Y))
和

#(str %1 %2)
是一樣的

那多個參數的相加,就可以寫簡成這樣了!#(reduce + %)

不負責任翻譯

(fn [_裡面可能有多個參數](reduce(+(_裡面可能有多個參數))
                   eg:(reduce +(range 1 101))

我們可以把sum寫成:

 (def sum #(reduce + %))

拿來用用看,比+單純只能對於numbers加減,

(sum [1 3 5 7 9])
=> 25

(sum (range 1 101))
=> 5050

自製的sum是不是很好用呢?:D

延伸思考

  1. 本文的 (reduce +(range 1 101)) 的reduce沒有提供initial value。為什麼reduce有的時候需要initial value,有的時候又不需要呢? (提示:可以參考文件:If val is supplied, returns the
    result of applying f to val and the first item in coll, then
    applying f to that result and the 2nd item, etc.)

  2. clojure有一個類似的reduction也可以拿來一起參考比較

https://ithelp.ithome.com.tw/upload/images/20220924/201111777gDPktRK6Y.png

例如本文的舉例: 1加到100

(reductions + (range 1 101))
=> (1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 210 231 253 276 300 325 351 378 406 435 465 496 528 561 595 630 666 703 741 780 820 861 903 946 990 1035 1081 1128 1176 1225 1275 1326 1378 1431 1485 1540 1596 1653 1711 1770 1830 1891 1953 2016 2080 2145 2211 2278 2346 2415 2485 2556 2628 2701 2775 2850 2926 3003 3081 3160 3240 3321 3403 3486 3570 3655 3741 3828 3916 4005 4095 4186 4278 4371 4465 4560 4656 4753 4851 4950 5050)

以上可看出會把每個range的step (在本例是每一步加的過程)
都放在sequence裡回傳

(class(reductions + (range 1 101)))
=> clojure.lang.LazySeq

如果有initial value的話,會從initial value開始加,且initial value也會放在sequence裡回傳

(reductions + 0 (range 1 101))
(0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 210 231 253 276 300 325 351 378 406 435 465 496 528 561 595 630 666 703 741 780 820 861 903 946 990 1035 1081 1128 1176 1225 1275 1326 1378 1431 1485 1540 1596 1653 1711 1770 1830 1891 1953 2016 2080 2145 2211 2278 2346 2415 2485 2556 2628 2701 2775 2850 2926 3003 3081 3160 3240 3321 3403 3486 3570 3655 3741 3828 3916 4005 4095 4186 4278 4371 4465 4560 4656 4753 4851 4950 5050)
  1. (reduce + (range 1 101)) 的例子情境,也可以用 (apply + (range 1 101)) 來取代

https://ithelp.ithome.com.tw/upload/images/20220924/20111177O6AI656xG1.png

clojure真的有好多種寫法呀~~~!!太神奇了 :P


上一篇
[Day08] Clojure Functional Programming與資料操作(1) - map
下一篇
[Day10] Clojure Functional Programming與資料操作(3) - filter
系列文
後端Developer實戰ClojureScript: Reagent與前端框架 Reframe30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言